package bongo.framework.gameobject;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import bongo.framework.annotations.Paintable;
import bongo.framework.annotations.RequiresComponent;
import bongo.framework.annotations.Updatable;
import bongo.framework.gameobject.component.Component;

/**
 * Generic GameObject
 * 
 * @author boogie
 * 
 */
public class GameObject implements Serializable {
    private static final long serialVersionUID = 1L;

    private final Map<Class<? extends Component>, Component> components;
    private final Map<Class<? extends Component>, Class<? extends Component>[]> dependecies;

    private final List<Component> updatables;
    private final List<Component> paintables;
    private final List<GameObject> children;
    
    private GameObject parent;
    

    public GameObject() {
	components = new HashMap<Class<? extends Component>, Component>();
	dependecies = new HashMap<Class<? extends Component>, Class<? extends Component>[]>();
	updatables = new ArrayList<Component>();
	paintables = new ArrayList<Component>();
	children = new ArrayList<GameObject>();
    }

    /**
     * <p>
     * Add a {@link Component} to this GameObject. If a Component of that exact type already exists that then the method does not atempt to add it again
     * </p>
     * <p>
     * If the Component to be added requires other components in order to function (see {@link RequiresComponent}) the a {@link GameObjectException} is thrown.	
     * </p>
     * @param c
     */
    public void addComponent(Component c) {
	if (components.containsKey(c.getClass())) {
	    return;
	}
	Component clone = c.cloneForGameObject(this);
	RequiresComponent req = clone.getClass().getAnnotation(
		RequiresComponent.class);
	if (req != null) {
	    Class<? extends Component>[] reqTypes = req.value();
	    for (Class<? extends Component> key : reqTypes) {
		if (!components.containsKey(key)) {
		    throw new GameObjectException(
			    "Requirements not met. Component "
				    + clone.getClass().getName() + " requires "
				    + key.getName() + " to function");
		}
	    }
	    this.dependecies.put(clone.getClass(), reqTypes);
	}

	clone.setGameObject(this);
	Updatable updatable = clone.getClass().getAnnotation(Updatable.class);
	if (updatable != null) {
	    this.updatables.add(clone);
	}
	Paintable paintable = clone.getClass().getAnnotation(Paintable.class);
	if (paintable != null) {
	    this.paintables.add(clone);
	}

	components.put(clone.getClass(), clone);
	clone.init();
    }

    public <T extends Component> T getComponent(Class<T> type) {
	Component c = components.get(type);
	return c == null ? null : type.cast(c);
    }

    /**
     * <p>
     * Tries to remove the {@link Component} of the specified type.
     * </p>
     * <p>
     * If the Component to be removed is required by any other components in this class (see {@link RequiresComponent})
     * a {@link GameObjectException} is thrown, else the Component is removed
     * </p>
     * @param type
     */
    public void removeComponent(Class<? extends Component> type) {
	for (Component c : this.components.values()) {
	    Class<? extends Component> clazz = c.getClass();
	    RequiresComponent r = clazz.getAnnotation(RequiresComponent.class);
	    if (r == null) {
		continue;
	    }
	    Class<? extends Component>[] reqTypes = r.value();
	    for (Class<? extends Component> req : reqTypes) {
		if (req.equals(type)) {
		    throw new GameObjectException("Component " + type.getName()
			    + " is required for " + clazz.getName()
			    + " to function!");
		}
	    }
	}
	components.remove(type);
    }
    
    
    /**
     * Adds a child GameObject to this GameObject
     * The child GameObject (and all other) will be updated and painted before this GameObject
     * @param g
     */
    public void addChild(GameObject g){
	this.children.add(g);
	g.setParrent(this);
    }
    /**
     * Removes the GameObject from this GameObject
     * this child's update and paint methods will no longer be called automatically 
     * @param g
     */
    public void removeChild(GameObject g){
	this.children.remove(g);
	g.setParrent(null);
    }
    
    
    /**
     * Sets the parent of this game object
     * @param parent
     */
    protected void setParrent(GameObject parent){
	this.parent = parent;
    }
    /**
     * @return the parent GameObject of this GameObject or null if this is has no parent
     */
    public GameObject parent(){
	return this.parent;
    }

    /**
     * Destroys this GameObject and all its {@link Component}s
     */
    public void destroy() {
	for (GameObject g : children) {
	    g.destroy();
	}
	for (Component c : this.components.values()) {
	    c.destroy();
	}
	this.components.clear();
	this.dependecies.clear();
	this.updatables.clear();
	this.paintables.clear();
	this.children.clear();
	this.setParrent(null);
    }
    

    /**
     * Updates all the child GameObject of this GameObject and then
     * updates all {@link Component}s of this GameObject marked as
     * {@link Updatable}
     * 
     * @param delta
     */
    public void update(float delta) {
	for(GameObject g : children){
	    g.update(delta);
	}
	for (Component c : updatables) {
	    c.update(delta);
	}
    }

    /**
     * Paints all the child GameObjects of this GameObject and then
     * paints all the {@link Component}s of this GameObject marked as
     * {@link Paintable}
     * 
     * @param alpha
     */
    public void paint(float alpha) {
	for (GameObject g : children) {
	    g.paint(alpha);
	}
	for (Component c : paintables) {
	    c.paint(alpha);
	}
    }

}
